/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.properties;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import liquibase.pro.packaged.T;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.function.SingletonSupplier;

/**
 * 用于将值从指定的源映射到目标的工具类。
 * 主要用于辅助将 {@link ConfigurationProperties @ConfigurationProperties} 的值映射到第三方类。
 * <p>
 * 可以基于谓词过滤值，并在需要时对值进行转换。例如：
 * <pre class="code">
 * PropertyMapper map = PropertyMapper.get();
 * map.from(source::getName)
 *   .to(destination::setName);
 * map.from(source::getTimeout)
 *   .whenNonNull()
 *   .asInt(Duration::getSeconds)
 *   .to(destination::setTimeoutSecs);
 * map.from(source::isEnabled)
 *   .whenFalse()
 *   .toCall(destination::disable);
 * </pre>
 * <p>
 * 映射最终可以应用到 {@link Source#to(Consumer) setter}、触发 {@link Source#toCall(Runnable) 方法调用} 或创建 {@link Source#toInstance(Function) 新实例}。
 *
 * @author Phillip Webb
 * @author Artsiom Yudovin
 * @since 2.0.0
 */
public final class PropertyMapper {

	private static final Predicate<?> ALWAYS = (t) -> true;

	private static final PropertyMapper INSTANCE = new PropertyMapper(null, null);

	private final PropertyMapper parent;

	private final SourceOperator sourceOperator;

	private PropertyMapper(PropertyMapper parent, SourceOperator sourceOperator) {
		this.parent = parent;
		this.sourceOperator = sourceOperator;
	}

	/**
	 * 返回一个新的 {@link PropertyMapper} 实例，该实例对每个 source 应用
	 * {@link Source#whenNonNull() whenNonNull} 过滤。
	 * @return 新的属性映射器实例
	 */
	public PropertyMapper alwaysApplyingWhenNonNull() {
		return alwaysApplying(this::whenNonNull);
	}

	private <T> Source<T> whenNonNull(Source<T> source) {
		return source.whenNonNull();
	}

	/**
	 * 返回一个新的 {@link PropertyMapper} 实例，该实例对每个 source 应用给定的
	 * {@link SourceOperator}。
	 * @param operator 要应用的 source 操作符
	 * @return 新的属性映射器实例
	 */
	public PropertyMapper alwaysApplying(SourceOperator operator) {
		Assert.notNull(operator, "Operator must not be null");
		return new PropertyMapper(this, operator);
	}

	/**
	 * 根据指定的值提供者返回一个新的 {@link Source}，用于执行映射操作。
	 * @param <T> 源类型
	 * @param supplier 值提供者
	 * @return 可用于完成映射的 {@link Source}
	 * @see #from(Object)
	 */
	public <T> Source<T> from(Supplier<T> supplier) {
		Assert.notNull(supplier, "Supplier must not be null");
		Source<T> source = getSource(supplier);
		if (this.sourceOperator != null) {
			source = this.sourceOperator.apply(source);
		}
		return source;
	}

	/**
	 * 根据指定的值返回一个新的 {@link Source}，用于执行映射操作。
	 * @param <T> 源类型
	 * @param value 值
	 * @return 可用于完成映射的 {@link Source}
	 */
	public <T> Source<T> from(T value) {
		return from(() -> value);
	}

	@SuppressWarnings("unchecked")
	private <T> Source<T> getSource(Supplier<T> supplier) {
		if (this.parent != null) {
			return this.parent.from(supplier);
		}
		return new Source<>(SingletonSupplier.of(supplier), (Predicate<T>) ALWAYS);
	}

	/**
	 * 返回属性映射器。
	 * @return 属性映射器实例
	 */
	public static PropertyMapper get() {
		return INSTANCE;
	}

	/**
	 * 可应用于 {@link Source} 的操作接口。
	 */
	@FunctionalInterface
	public interface SourceOperator {

		/**
		 * 对给定的源应用操作。
		 * @param <T> 源类型
		 * @param source 要操作的源
		 * @return 更新后的源
		 */
		<T> Source<T> apply(Source<T> source);

	}

	/**
	 * 正在进行映射的源。
	 *
	 * @param <T> 源类型
	 */
	public static final class Source<T> {

		private final Supplier<T> supplier;

		private final Predicate<T> predicate;

		private Source(Supplier<T> supplier, Predicate<T> predicate) {
			Assert.notNull(predicate, "Predicate must not be null");
			this.supplier = supplier;
			this.predicate = predicate;
		}

		/**
		 * 返回一个经过适配的源，其类型为 {@link Integer}。
		 * @param <R> 结果类型
		 * @param adapter 用于将当前值转换为数字的适配器
		 * @return 一个新的适配源实例
		 */
		public <R extends Number> Source<Integer> asInt(Function<T, R> adapter) {
			return as(adapter).as(Number::intValue);
		}

		/**
		 * 返回一个经过给定适配函数转换的适配源。
		 * @param <R> 结果类型
		 * @param adapter 适配器函数
		 * @return 一个新的适配源实例
		 */
		public <R> Source<R> as(Function<T, R> adapter) {
			Assert.notNull(adapter, "Adapter must not be null");
			Supplier<Boolean> test = () -> this.predicate.test(this.supplier.get());
			Predicate<R> predicate = (t) -> test.get();
			Supplier<R> supplier = () -> {
				if (test.get()) {
					return adapter.apply(this.supplier.get());
				}
				return null;
			};
			return new Source<>(supplier, predicate);
		}

		/**
		 * 返回一个过滤版本的源，不映射非空值或者抛出 {@link NullPointerException} 的供应者。
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> whenNonNull() {
			return new Source<>(new NullPointerExceptionSafeSupplier<>(this.supplier), Objects::nonNull);
		}

		/**
		 * 返回一个过滤版本的源，只映射值为 {@code true} 的。
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> whenTrue() {
			return when(Boolean.TRUE::equals);
		}

		/**
		 * 返回一个过滤版本的源，只映射值为 {@code false} 的。
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> whenFalse() {
			return when(Boolean.FALSE::equals);
		}

		/**
		 * 返回一个过滤版本的源，只映射那些 {@code toString()} 返回的字符串包含实际文本的值。
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> whenHasText() {
			return when((value) -> StringUtils.hasText(Objects.toString(value, null)));
		}

		/**
		 * 返回一个过滤版本的源，只映射等于指定对象的值。
		 * @param object 要匹配的对象
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> whenEqualTo(Object object) {
			return when(object::equals);
		}

		/**
		 * 返回一个过滤版本的源，只映射属于给定类型的值。
		 * @param <R> 目标类型
		 * @param target 要匹配的目标类型
		 * @return 一个新的过滤后的源实例
		 */
		public <R extends T> Source<R> whenInstanceOf(Class<R> target) {
			return when(target::isInstance).as(target::cast);
		}

		/**
		 * 返回一个过滤版本的源，不映射匹配给定谓词的值。
		 * @param predicate 用于过滤值的谓词
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> whenNot(Predicate<T> predicate) {
			Assert.notNull(predicate, "Predicate must not be null");
			return when(predicate.negate());
		}

		/**
		 * 返回一个过滤版本的源，只映射匹配给定谓词的值。
		 * @param predicate 用于过滤值的谓词
		 * @return 一个新的过滤后的源实例
		 */
		public Source<T> when(Predicate<T> predicate) {
			Assert.notNull(predicate, "Predicate must not be null");
			return new Source<>(this.supplier, (this.predicate != null) ? this.predicate.and(predicate) : predicate);
		}

		/**
		 * 完成映射，将未被过滤的值传递给指定的消费者。
		 * @param consumer 如果值未被过滤，接收该值的消费者
		 */
		public void to(Consumer<T> consumer) {
			Assert.notNull(consumer, "Consumer must not be null");
			T value = this.supplier.get();
			if (this.predicate.test(value)) {
				consumer.accept(value);
			}
		}

		/**
		 * 完成映射，通过工厂函数基于未被过滤的值创建一个新实例。
		 * @param <R> 返回的类型
		 * @param factory 用于创建实例的工厂函数
		 * @return 创建的实例
		 * @throws NoSuchElementException 如果值被过滤，则抛出异常
		 */
		public <R> R toInstance(Function<T, R> factory) {
			Assert.notNull(factory, "Factory must not be null");
			T value = this.supplier.get();
			if (!this.predicate.test(value)) {
				throw new NoSuchElementException("No value present");
			}
			return factory.apply(value);
		}

		/**
		 * 完成映射，在值未被过滤时调用指定的方法。
		 * @param runnable 当值未被过滤时调用的方法
		 */
		public void toCall(Runnable runnable) {
			Assert.notNull(runnable, "Runnable must not be null");
			T value = this.supplier.get();
			if (this.predicate.test(value)) {
				runnable.run();
			}
		}

	}

	/**
	 * 会捕获并忽略任何 {@link NullPointerException} 的供应者。
	 */
	private static class NullPointerExceptionSafeSupplier<T> implements Supplier<T> {

		private final Supplier<T> supplier;

		NullPointerExceptionSafeSupplier(Supplier<T> supplier) {
			this.supplier = supplier;
		}

		@Override
		public T get() {
			try {
				return this.supplier.get();
			}
			catch (NullPointerException ex) {
				return null;
			}
		}

	}

}
